#pragma code_seg("CSAMBA")
#pragma data_seg("DSAMBA")
#pragma bss_seg("BSAMBA")
#pragma const_seg("KSAMBA")
#pragma comment(linker, "/merge:DSAMBA=SAMBA")
#pragma comment(linker, "/merge:CSAMBA=SAMBA")
#pragma comment(linker, "/merge:BSAMBA=SAMBA")
#pragma comment(linker, "/merge:KSAMBA=SAMBA")
/*

    This file is part of the smb++ library

    Copyright (C) 2000  Nicolas Brodu

    nicolas.brodu@free.fr



    Portions of this file were copied or inspired from the Samba Unix

	SMB/Netbios implementation, and modified. Those sections were

	authored initially and Copyright (C) Andrew Tridgell 1994-1998.



    This program is free software; you can redistribute it and/or modify

    it under the terms of the GNU General Public License as published by

    the Free Software Foundation; either version 2 of the License, or

    (at your option) any later version.



    This program is distributed in the hope that it will be useful,

    but WITHOUT ANY WARRANTY; without even the implied warranty of

    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the

    GNU General Public License for more details.



    You should have received a copy of the GNU General Public License

    along with this program, see the file COPYING; if not, write

    to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,

    MA 02139, USA.

*/

#include "defines.h"

#ifdef USE_SAMBA



#ifndef _WIN32

#include <unistd.h>

#endif

#include <ctype.h>

#ifdef _WIN32

#include <winsock2.h>

#define S_ISDIR(x) (((x)&0xF000) == 0x4000)

#else

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#endif

#include <stdio.h>

#include <errno.h> // for EINVAL

#include "Resolve.h"

#include "strtool.h"

#include "SambaLink.h"

#include "SambaExterns.h"



#include <iostream.h>



static bool sambaIsLoaded = false;

int SambaLink::instances=0;



void SambaLink::loadSamba()

{

	if (!sambaIsLoaded) {

		TimeInit();

		charset_initialise();

		// must set up variables in libsamba !!!

		if (!get_myname(myhostname,0)) return;

		get_myname(global_myname,0);

		in_client = True;   // Make sure that we tell lp_load we are

		if (!lp_load(CONFIGFILE,True,False,False)) return;

		codepage_initialise(lp_client_code_page());

		pstring term_code = "";

		interpret_coding_system(term_code);  // KANJI => non-zero

		load_interfaces();

		sambaIsLoaded = true;

	}

}



// class defined here only for storing the results of Samba callback functions

// in directory listings (including virtual directories)

// Add a comment field, because we need the master information

// when browsing workgroups.

class SMBdirentList {

public:

	SMBdirentList(SMBdirent &aDirent, const char* commentarg) {

		entry = aDirent;

		comment = 0;

		newstrcpy(comment,commentarg);

		// add us to the simple-linked list, keep it sorted

		bool c = S_ISDIR(entry.st_mode);

		bool n = dirListing ? S_ISDIR(dirListing->entry.st_mode) : false;

		bool s = dirListing ? (mystrcmp(entry.d_name, dirListing->entry.d_name)<=0) : false;

		if (!dirListing

		|| (c && !n)

		|| (s && !c && !n)

		|| (s && c && n)

		) {

			next = dirListing;

			dirListing = this;

			return;

		} else {

			for (SMBdirentList* it = dirListing; (it); it=it->next) {

				bool c = S_ISDIR(entry.st_mode);

				bool n = (it->next) ? S_ISDIR(it->next->entry.st_mode) : false;

				bool s = (it->next) ? (mystrcmp(entry.d_name, it->next->entry.d_name)<=0) : false;

				if (!(it->next)

				|| (c && !n)

				|| (s && !c && !n)

				|| (s && c && n)

				) {

					next = it->next;

					it->next = this;

					return;

				}

			}

		}

	}

	// Do recursion in the destructor because lists could be stored

	// independantly => have to be careful to reset dirListing to 0

	// when necessary.

	~SMBdirentList() {

		if (comment) delete comment;

		comment = 0;

		if (next) delete next;

		next = 0;

	}

	// Merge ourselve in the last results

	// Though it's in O(n), there are O(n) copies/deletion that could be

	// avoided. But I won't bother rewriting it all now...

	void merge() {

		if (this == dirListing) return; // merge with ourselve done!

		SMBdirentList *ori = dirListing; // take it for us

		dirListing = 0;

		// make a deep copy of ourselve first

		for (SMBdirentList *it = this; (it); it = it->next) {

			new SMBdirentList(it->entry, it->comment);

		}

		// add the original list to it

#ifdef _WIN32

		for (it = ori; (it); it = it->next) {

#else

		for (SMBdirentList *it = ori; (it); it = it->next) {

#endif

			new SMBdirentList(it->entry, it->comment);

		}

		// We don't need the original anymore

		if (ori) delete ori;

		// remove the duplicates

		// dirListing is non-empty at this stage, we added 'this' to it

#ifdef _WIN32

		for (it = dirListing; (it) && (it->next); it = it->next) {

#else

		for (SMBdirentList *it = dirListing; (it) && (it->next); it = it->next) {

#endif

			while ((it->next) && (!mystrcmp(it->next->entry.d_name, it->entry.d_name))) {

				SMBdirentList *tmp = it->next;

				it->next = it->next->next;

				tmp->next = 0; // stop recursion

				delete tmp;

			}

		}

	}

	// Empty the last list of result. Use the recursion in the destructor.

	static void empty() {

		if (dirListing) delete dirListing;

		dirListing=0;

	}

	// Return the comment associated with this name in the list

	static char* nameComment(const char *name, bool caseSensitive = false) {

		for (SMBdirentList *it = dirListing; (it); it = it->next) {

			if ((caseSensitive && !mystrcmp(name, it->entry.d_name))

			||  (!caseSensitive && !mystrcasecmp(name, it->entry.d_name))) {

				return it->comment;

			}

		}

		return 0;

	}

	SMBdirent entry;

	char *comment;

	SMBdirentList *next;

// static again, because of the callback.

// WARNING: THIS IS NOT RE-ENTRANT. THREADS COULD HAVE BIG PROBLEMS!!!

	static SMBdirentList *dirListing;

};

// Ok, the callback cannot access a specific Descriptor*

// It will store the information here. Same comment as above for browser()

SMBdirentList *SMBdirentList::dirListing = 0;



// Utility class for all descriptor

class Descriptor {

private:

	int myNumber;

	Descriptor *beforeMe;

	Descriptor *afterMe;

	char *myFile;

	cli_state *myCli;

public:

	Descriptor(const char* file, cli_state* cli, int sambafd);

	~Descriptor();

	cli_state *cli();

	int fid;

	char *file();

	// directory descriptor

	bool dirdesc;

	// used for readdir, since Samba gets all the results at one time

	SMBdirentList *dirList;  // the whole list, if any

	SMBdirentList *pdirList;  // pointer to the current element of that list

	uint32 pos;

	operator const int() const;

	Descriptor *next();

	// number of the last created descriptor

	static Descriptor *find(int num);

	static int descCount;

	static Descriptor *descriptorList;

};



// Init static values to something

Descriptor *Descriptor::descriptorList=0;

int Descriptor::descCount=10000;



Descriptor::Descriptor(const char* file, cli_state* cli, int sambafd)

{

	// Link ourself to the other descriptors

	beforeMe = 0;

	afterMe = descriptorList;

	descriptorList = this;

	if (afterMe) afterMe->beforeMe = this;

	// get a number

	myNumber = descCount++;

	// record our info

	myFile=0; newstrcpy(myFile,file);

	myCli = cli;

	fid = sambafd;

	pos = 0;

	dirList = 0;

	pdirList = 0;

	dirdesc = false;

}



Descriptor::~Descriptor()

{

	// unlink from previous element, or update list

	if (!beforeMe) descriptorList = afterMe;

	else beforeMe->afterMe = afterMe;

	// unlink from next element

	if (afterMe) afterMe->beforeMe = beforeMe;

	// cleanup

	if (myCli) {

		cli_close(myCli,fid);

		cli_shutdown(myCli);

		delete myCli;

	}

	if (myFile) delete myFile;

	if (dirList) delete dirList; // recursive deletion

}



Descriptor::operator const int() const

{

	return myNumber;

}



Descriptor *Descriptor::next()

{

	return afterMe;

}



cli_state *Descriptor::cli()

{

	return myCli;

}



char* Descriptor::file()

{

	return myFile;

}



Descriptor *Descriptor::find(int num)

{

	for (Descriptor* it=descriptorList; (it); it = it->next()) {

		if ( *it == num ) return it;

	}

	return 0;

}





SambaLink::SambaLink()

{

	loadSamba();

	instances++;

	lastError = 0;

	theMaster = 0;

}



SambaLink::~SambaLink()

{

	if (--instances<=0) {

		// Destructors are wonderful!

		while (Descriptor::descriptorList) delete Descriptor::descriptorList;

	}

	if (theMaster) delete theMaster;

}



#ifdef _WIN32

struct cli_state *SambaLink::connectUtil(const char *toparse)

#else

struct cli_state *SambaLink::connectUtil(const char *toparse = 0)

#endif

{

	// parse the argument if it was not done before

	if (toparse) util.parse(toparse);

	char *server = util.host();

	char *share = util.share();

	char *guest = "GUEST";  // define this here to have a valid address

	// cannot connect to nothing

	if ((!server) || (!share)) return 0;

	

	// Let's see if host really exists

	Resolve r;

	if (!r.gethostbyname(server)) return 0;

	

	struct cli_state *smb_cli;

	struct nmb_name called, calling, stupid_smbserver_called;

	struct in_addr ip;



	if ((smb_cli=cli_initialise(0)) == 0) return 0;



	make_nmb_name(&calling, global_myname, 0x0, "");

	make_nmb_name(&called, server, name_type, "");

	make_nmb_name(&stupid_smbserver_called , "*SMBSERVER", 0x20, scope);



	char *user = util.user();

	if (!user) user = userName; // No user specified: default from Options

	fstrcpy(smb_cli->usr.user_name, user);

	fstrcpy(smb_cli->usr.domain, lp_workgroup());



	char *hostip = util.ip();

#ifdef _WIN32

	if (hostip) ip.s_addr = inet_addr(hostip);

	else ip.s_addr = inet_addr("0.0.0.0");

#else

	if (hostip) inet_aton(hostip, &ip);

	else inet_aton("0.0.0.0", &ip);

#endif



	if (cli_set_port(smb_cli, 139) == 0) return 0;

	

	char *pass = util.password();

	// empty pass => null pass

	if ((pass) && (pass[0]==0)) pass=0;

	

	// If a password is specified, use it now

	if (pass) {

		pwd_init(&(smb_cli->usr.pwd));

		pwd_make_lm_nt_16(&(smb_cli->usr.pwd), pass);

	}

	else pwd_set_nullpwd(&(smb_cli->usr.pwd));



	smb_cli->use_ntlmv2 = lp_client_ntlmv2();

	

	struct cli_state *retval = smb_cli;

	

	if (!cli_establish_connection(smb_cli, server, &ip, &calling, &called,

	                              share, "?????", False, True) &&

	    !cli_establish_connection(smb_cli, server, &ip,

	                              &calling, &stupid_smbserver_called,

	                              share, "?????", False, True))

	{

		// Couldn't connect. Host exist but we don't know about share

		// Ask a user/pass even if it's for an inexistant share for now...

		retval = 0;



		// Save the old values

		char *savUser = 0, *savPass = 0;

		newstrcpy(savUser, user);

		newstrcpy(savPass, pass);

		

		// Now, check if we are in user level security

		if (smb_cli->sec_mode & 1) {

			// Yes, get a user for this server

			if ((!user) && (theCallback))

				user = theCallback->getAnswer(ANSWER_USER_NAME, server);

			// We _need_ a user in user security => try guest

			if ((!user) || (user[0]==0)) user = guest;

			// We suppose the account doesn't have an empty password,

			// so let's ask for a password right now and don't try an empty one

			// first (the answer might still be an empty pass anyway)

			if ((!pass) && (theCallback))

				pass = theCallback->getAnswer(ANSWER_USER_PASSWORD, user);

			// empty pass => null pass

			if ((pass) && (pass[0]==0)) pass=0;

		// Else it's a share level security => don't bother with the user

		} else {

			if ((!pass) && (theCallback))

				pass = theCallback->getAnswer(ANSWER_SERVICE_PASSWORD, share);

			// empty pass => null pass

			if ((pass) && (pass[0]==0)) pass=0;

		}

		

		// if the situation has changed, try again

		if ((!savUser && user) || (savUser && !user)

		|| (user && strcmp(savUser,user))

		|| (!savPass && pass) || (savPass && !pass)

		|| (pass && strcmp(savPass,pass))) {



			// set the new username/password

			fstrcpy(smb_cli->usr.user_name, user);

			if (pass) {

				pwd_init(&(smb_cli->usr.pwd));

				pwd_make_lm_nt_16(&(smb_cli->usr.pwd), pass);

			}

			else pwd_set_nullpwd(&(smb_cli->usr.pwd));

			

			// Try again

			if (cli_establish_connection(smb_cli, server, &ip, &calling, &called,

										share, "?????", False, True) ||

				cli_establish_connection(smb_cli, server, &ip,

										&calling, &stupid_smbserver_called,

										share, "?????", False, True))

				retval = smb_cli;

		}

		if (savPass) delete savPass;

		if (savUser) delete savUser;

	}

	return retval;

}





// find the error for this client, always returns -1

// Used in any of the functions when an error occurs

#ifdef _WIN32

int SambaLink::findError(struct cli_state *cli)

#else

int SambaLink::findError(struct cli_state *cli = 0)

#endif

{

	if (!cli) lastError = EINVAL;

	else lastError = cli_error(cli, 0, 0);

	return -1;

}



// set the lastError to an error code, always returns -1

// Used in any of the functions when an error occurs

int SambaLink::findError(int errcode)

{

	lastError = errcode;

	return -1;

}



// set the lastError to 0, always returns its argument

// Used in any of the functions when all goes well

#ifdef _WIN32

int SambaLink::noError(int ret)

#else

int SambaLink::noError(int ret=0)

#endif

{

	lastError = 0;

	return ret;

}



// Those functions work like their standard equivalent,

// but file descriptors are for internal use only.

// They accept smbURLs as parameters

#ifdef _WIN32

int SambaLink::open(const char* file, int flags, int mode)

#else

int SambaLink::open(const char* file="", int flags=O_RDWR, int mode=0644)

#endif

{

	util.parse(file);

	// We want a real file, host, share,... do not qualify

	if (!util.path()) return findError(EISDIR);

	

	struct cli_state *cli = connectUtil();

	if (!cli) return findError();  // connection failed



	// Convert util.path() into DOS\PATH\FILE

	char *dospath=0;

	newstrcpy(dospath, util.path());

	for (char* p=dospath; (*p); p++) {

		if (*p == '/') *p = '\\';

	}

	int sambafd = cli_open(cli, dospath, flags, DENY_NONE);

	delete dospath;

	if (sambafd == -1) {

		findError(cli);

		cli_shutdown(cli);

		return -1;

	}

	

	Descriptor *ret = new Descriptor(file, cli, sambafd);



	// append requested, must find the size of the file

	if (flags & O_APPEND) {

		uint16 attr;

		size_t size;

		time_t ctime, atime, mtime;

		int res = cli_getattrE(cli, sambafd, &attr, &size, &ctime, &atime, &mtime);

		if (res == False) return findError(cli);

		ret->pos = size;

	}

	

	return noError(*ret);

}



#ifdef _WIN32

int SambaLink::creat(const char* file, int mode)

#else

int SambaLink::creat(const char* file="", int mode=0644)

#endif

{

	return open(file, O_CREAT|O_WRONLY|O_TRUNC, mode);

}



int SambaLink::stat(const char *filename, struct stat *buf)

{

	util.parse(filename);

	// If it's real file, connect and send a getatr SMB command

	if (util.path()) {

		struct cli_state *cli = connectUtil();

		uint16 attr;

		size_t size;

		time_t theTime;

		

		// Convert util.path() into DOS\PATH\FILE

		char *dospath=0;

		newstrcpy(dospath, util.path());

		for (char* p=dospath; (*p); p++) {

			if (*p == '/') *p = '\\';

		}

		int res = cli_getatr(cli, dospath, &attr, &size, &theTime);

		delete dospath;

		if (res == False) {

			findError(cli);

			cli_shutdown(cli);

			return -1;

		}

		// archive attribute mapped to 0644

		buf->st_mode=0644;

		if (attr&0x20) buf->st_mode|=0644;

		// read-only attribute removes write permission

		if (attr&0x01) buf->st_mode&=077666;

		// directory attribute mapped to 040755

		if (attr&0x10) buf->st_mode|=040755;

		// Anyone has any idea about what volume, hidden and

		// system should be mapped to ???

		buf->st_ctime = buf->st_ctime = buf->st_ctime = theTime;

#ifdef _WIN32

		buf->st_uid = 500;

		buf->st_gid = 500;

#else

		buf->st_uid = getuid();

		buf->st_gid = getgid();

#endif

		buf->st_rdev=0100000; // regular file

		buf->st_size = size;

		cli_shutdown(cli);

	

	// Else it's a virtual directory (or the root directory)

	} else {

		buf->st_mode=040755; // directory

#ifdef _WIN32

		buf->st_uid=500;

		buf->st_gid=500;

#else

		buf->st_uid=getuid();

		buf->st_gid=getgid();

#endif

		buf->st_size=0; // Not important for a directory anyway

		buf->st_ctime=buf->st_mtime=buf->st_atime=0;

		buf->st_rdev=0100000; // regular file

	}	

	return noError();

}



int SambaLink::fstat(int fd, struct stat *buf)

{

	Descriptor *d = Descriptor::find(fd);

	if ((!d) || (d->dirdesc)) return findError(EBADF);

	

	uint16 attr;

	size_t size;

	int res = cli_getattrE(d->cli(), d->fid, &attr, &size,

		 &buf->st_ctime, &buf->st_atime, &buf->st_mtime);

	if (res == False) return findError(d->cli());

	buf->st_size = size;

	// archive attribute mapped to 0644

	buf->st_mode=0644;

	if (attr&0x20) buf->st_mode|=0644;

	// read-only attribute removes write permission

	if (attr&0x01) buf->st_mode&=077666;

	// directory attribute mapped to 040755

	if (attr&0x10) buf->st_mode|=040755;

	// Anyone has any idea about what volume, hidden and

	// system should be mapped to ???

#ifdef _WIN32

#else

	buf->st_uid = 500;

	buf->st_gid = 500;

#endif

	buf->st_rdev=0100000; // regular file

	

	return noError();

}



// read and write do caching !

int SambaLink::read(int fd, void *buf, uint32 count)

{

	Descriptor *d = Descriptor::find(fd);

	if ((!d) || (d->dirdesc)) return findError(EBADF);

	

	// caching already done in Samba :-)

	int ret = cli_read(d->cli(), d->fid, (char*)buf, d->pos, count, True);

	

	// update position in file

	if (ret>0) d->pos += ret;

	else findError(d->cli());

	

	return noError(ret);

}



int SambaLink::write(int fd, void *buf, uint32 count)

{

	Descriptor *d = Descriptor::find(fd);

	if ((!d) || (d->dirdesc)) return findError(EBADF);

	

	// caching already done in Samba :-)

	int ret = cli_write(d->cli(), d->fid, 0, (char*)buf, d->pos, count, 0);

	

	// update position in file

	if (ret>0) d->pos += ret;

	

	return noError(ret);

}



// flush forces a write of all buffered data for the given fd

int SambaLink::flush(int fd)

{

	Descriptor *d = Descriptor::find(fd);

	if ((!d) || (d->dirdesc)) return findError(EBADF);

	

	// Use Samba "disallow write caching" function and write 0 bytes!

	char buf; // To be on the safe side... make a pointer in our address space!

	int ret = cli_write(d->cli(), d->fid, 1, &buf, d->pos, 0, 0);

	if (ret==-1) return findError(d->cli());

	else return noError();

}



int32 SambaLink::lseek(int fd, int32 offset, int from)

{

	Descriptor *d = Descriptor::find(fd);

	if ((!d) || (d->dirdesc)) return findError(EBADF);

	

	int res = True;  // must declare it here because of the case label

	switch (from) {

		case SEEK_SET:

			d->pos = offset;

			break;

		case SEEK_CUR:

			d->pos += offset;

			break;

		case SEEK_END:

			uint16 attr;

			size_t size;

			time_t c_time, a_time, m_time;

			res = cli_getattrE(d->cli(), d->fid, &attr, &size, &c_time, &a_time, &m_time);

			if (res == False) return findError(d->cli());

			d->pos = size + offset;

			break;

		default:

			return findError(EINVAL);

	}

		

	return noError(d->pos);

}



int SambaLink::close(int fd)

{

	Descriptor *d = Descriptor::find(fd);

	if ((!d) || (d->dirdesc)) return findError(EBADF);

	delete d;  // also close and shut the client down

	return noError();

}



// unlink sends a unlink SMB. Will delete or unlink depending on target OS

int SambaLink::unlink(const char *file)

{

	util.parse(file);

	// We want a real file, host, share,... do not qualify

	if (!util.path()) return findError(EPERM);

	

	struct cli_state *cli = connectUtil();

	if (!cli) return findError();  // connection failed



	// Convert util.path() into DOS\PATH\FILE

	char *dospath=0;

	newstrcpy(dospath, util.path());

	for (char* p=dospath; (*p); p++) {

		if (*p == '/') *p = '\\';

	}

	BOOL ret = cli_unlink(cli, dospath);

	delete dospath;

	if (ret == False) {

		findError(cli);

		cli_shutdown(cli);

		return -1;

	}

	return noError();

}



// Renames a file, but doesn't move it between directories

int SambaLink::rename(const char *fileURL, const char *newname)

{

	util.parse(fileURL);

	// We want a real file, host, share,... do not qualify

	if (!util.path()) return findError(EPERM);

	

	struct cli_state *cli = connectUtil();

	if (!cli) return findError();  // connection failed

	

	// Convert util.path() into DOS\PATH\FILE. Look for the last token (file name)

	char *dospath=0;

	newstrcpy(dospath, util.path());

	char *lastToken = dospath;

	for (char* p=dospath; (*p); p++) {

		if (*p == '/') {*p = '\\'; lastToken = p;}

	}

	int len = lastToken-dospath;

	char *destpath = new char[len+1];

	if (len) memcpy(destpath,dospath,len);

	destpath[len]=0;

/*	char* upname = 0;

	newstrcpy(upname, newname);

	for (char* p=upname; (*p); p++) *p = toupper(*p);

	*/

	if (len) newstrappend(destpath,"\\",newname);

	else newstrcpy(destpath,newname);

//	delete upname;



	BOOL ret = cli_rename(cli, dospath, destpath);

	delete destpath;

	if (ret == False) {

		findError(cli);

		cli_shutdown(cli);

		return -1;

	}

	return noError();

}



// deletes a directory

int SambaLink::rmdir(const char *pathname)

{

	util.parse(pathname);

	// We want a real file, host, share,... do not qualify

	if (!util.path()) return findError(EPERM);

	

	struct cli_state *cli = connectUtil();

	if (!cli) return findError();  // connection failed

	

	// Convert util.path() into DOS\PATH\FILE

	char *dospath=0;

	newstrcpy(dospath, util.path());

	for (char* p=dospath; (*p); p++) {

		if (*p == '/') *p = '\\';

	}

	BOOL ret = cli_rmdir(cli, dospath);

	delete dospath;

	if (ret == False) {

		findError(cli);

		cli_shutdown(cli);

		return -1;

	}

	return noError();

}



// creates a directory

int SambaLink::mkdir(const char *pathname)

{

	util.parse(pathname);

	// We want a real file, host, share,... do not qualify

	if (!util.path()) return findError(EACCES);

	

	struct cli_state *cli = connectUtil();

	if (!cli) return findError();  // connection failed

	

	// Convert util.path() into DOS\PATH\FILE

	char *dospath=0;

	newstrcpy(dospath, util.path());

	for (char* p=dospath; (*p); p++) {

		if (*p == '/') *p = '\\';

	}

	BOOL ret = cli_mkdir(cli, dospath);

	delete dospath;

	if (ret == False) {

		findError(cli);

		cli_shutdown(cli);

		return -1;

	}

	return noError();

}





// AH-AH!!! If you read everything this far, you probably deserve a coffe break

// before reading further on.



// Samba needed callback for listing directories. Use mask as comment!

static void dirLister(file_info *info, const char *mask)

{

	SMBdirent entry;

	newstrcpy(entry.d_name, dos_to_unix(info->name, False));

	entry.st_rdev=0100000; // regular file

	// archive attribute mapped to 0644

	entry.st_mode=0644;

	if (info->mode&0x20) entry.st_mode|=0644;

	// read-only attribute removes write permission

	if (info->mode&0x01) entry.st_mode&=077666;

	// directory attribute mapped to 040755

	if (info->mode&0x10) entry.st_mode|=040755;

	// Anyone has any idea about what volume, hidden and

	// system should be mapped to ???

	entry.st_ctime = info->ctime;

	entry.st_atime = info->atime;

	entry.st_mtime = info->mtime;

	entry.st_uid = info->uid;

	entry.st_gid = info->gid;

	entry.st_size = info->size;

	// Create new SMBdirent entry that adds itself to dirListing

	// Cannot manage memory from this callback => BE CAUTIOUS

	new SMBdirentList(entry, mask);

}



// shareBrowser is used for shares, it filters out printers, IPC, ...

static void shareBrowser(const char *name, uint32 m, const char *comment)

{

	if (m != STYPE_DISKTREE) return; // filter out printers, IPC, ...

	SMBdirent entry;

	newstrcpy(entry.d_name, name);

	entry.st_mode=040755; // directory

#ifdef _WIN32

	entry.st_uid=500;

	entry.st_gid=500;

#else

	entry.st_uid=getuid();

	entry.st_gid=getgid();

#endif

	entry.st_size=0; // Not important for a directory anyway

	entry.st_ctime=entry.st_mtime=entry.st_atime=0;

	entry.st_rdev=0100000; // regular file

	// Create new SMBdirent entry that adds itself to dirListing

	// Cannot manage memory from this callback => BE CAUTIOUS

	new SMBdirentList(entry, comment); // get rid of the 2nd 'uint32' argument

}



// netBrowser is used for members & workgroups and don't filter anything

static void netBrowser(const char *name, uint32 m, const char *comment)

{

	SMBdirent entry;

	newstrcpy(entry.d_name, name);

	entry.st_mode=040755; // directory

#ifdef _WIN32

	entry.st_uid=500;

	entry.st_gid=500;

#else

	entry.st_uid=getuid();

	entry.st_gid=getgid();

#endif

	entry.st_size=0; // Not important for a directory anyway

	entry.st_ctime=entry.st_mtime=entry.st_atime=0;

	entry.st_rdev=0100000; // regular file

	// Create new SMBdirent entry that adds itself to dirListing

	// Cannot manage memory from this callback => BE CAUTIOUS

	new SMBdirentList(entry, comment); // get rid of the 2nd 'uint32' argument

}





// Get the share list of the host in the url.

// Use the full url because a user/pass might be in it

#ifdef _WIN32

int SambaLink::getShareList(const char* toparse)

#else

int SambaLink::getShareList(const char* toparse = 0)

#endif

{

	// parse the argument if it was not done before

	if (toparse) util.parse(toparse);

	// overwrite the possible share

	util.share("IPC$");

	// Empty the result list

	SMBdirentList::empty();

	// connect and do the job

	struct cli_state *cli = connectUtil();

	if (!cli) return findError();

	if (!cli_RNetShareEnum(cli, shareBrowser)) return findError(cli);

	cli_shutdown(cli);

	return noError();

}



// Get the workgroup list according to the host in the url.

// Use the full url because a user/pass might be in it

#ifdef _WIN32

int SambaLink::getWorkgroupList(const char* toparse)

#else

int SambaLink::getWorkgroupList(const char* toparse = 0)

#endif

{

	// parse the argument if it was not done before

	if (toparse) util.parse(toparse);

	// overwrite the possible share

	util.share("IPC$");

	// Empty the result list

	SMBdirentList::empty();

	// connect and do the job

	struct cli_state *cli = connectUtil(toparse);

	if (!cli) return findError();

	if (!cli_NetServerEnum(cli, util.workgroup(), SV_TYPE_DOMAIN_ENUM, netBrowser))

		return findError(cli);

	cli_shutdown(cli);

	return noError();

}



// Get the member of workgroup list according to the host in the url.

// Use the full url because a user/pass might be in it

#ifdef _WIN32

int SambaLink::getMemberList(const char* toparse)

#else

int SambaLink::getMemberList(const char* toparse = 0)

#endif

{

	// parse the argument if it was not done before

	if (toparse) util.parse(toparse);

	// overwrite the possible share

	util.share("IPC$");

	// Empty the result list

	SMBdirentList::empty();

	// connect and do the job

	struct cli_state *cli = connectUtil(toparse);

	if (!cli) return findError();

	if (!cli_NetServerEnum(cli, util.workgroup(), SV_TYPE_ALL, netBrowser))

		return findError(cli);

	cli_shutdown(cli);

	return noError();

}





// opendir returns a descriptor as well.

int SambaLink::opendir(const char *name)

{

	util.parse(name);

	// Easy first case: virtual directory

	if (!util.share()) {

		Descriptor *ret = new Descriptor(name, 0, 0);

		ret->dirdesc = true;

		return noError(*ret);

	}



	// Is it really a directory?

	struct stat buf;

	if (stat(name, &buf)==-1) return -1; // error already set

	if (!S_ISDIR(buf.st_mode)) return findError(ENOTDIR);

	

	// So now we open the connection to the host

	struct cli_state *cli = connectUtil();

	if (!cli) return findError();  // connection failed

	

	// And store the client state structure in a descriptor for later use

	Descriptor *ret = new Descriptor(name, cli, 0);

	ret->dirdesc = true;

	return noError(*ret);

}





// see types.h for SMBdirent description

SMBdirent *SambaLink::readdir(int dirdesc)

{

	Descriptor *d = Descriptor::find(dirdesc);

	if ((!d) || (!d->dirdesc)) {

		findError(EBADF);

		return 0;

	}



	// fid is used to indicate there already was a connection

	if (d->fid) {

		// The only way to know it's an end-of-dir for the caller

		noError(); // and not an error is to look at the error code

		// list might still be empty (empty dirs)

		if (!(d->dirList)) return 0;

		if (d->pdirList) {

			SMBdirent *ret = &(d->pdirList->entry);

			d->pdirList = d->pdirList->next;  // increment counter

			return ret;

		}

		return 0; // end of directory

	}

	

	// cli exist => real directory

	if (d->cli()) {

		util.parse(d->file());

		// Convert util.path() into DOS\PATH

		char *dosmask=0;

		newstrcpy(dosmask, util.path());

		for (char* p=dosmask; (p && (*p)); p++) {

			if (*p == '/') *p = '\\';

		}

		// Append the * mask => want all files

		newstrappend(dosmask,"\\*");

		// Empty the result list

		SMBdirentList::empty();

		// 0x02|0x04|0x10: include hidden, system, directory

		cli_list(d->cli(), dosmask, 0x02|0x04|0x10, dirLister);

		// The descriptor becomes the owner of the list

		d->dirList = SMBdirentList::dirListing;

		SMBdirentList::dirListing = 0;

		// Set the pointer at the begining

		d->pdirList = d->dirList;

		d->fid = 1; // set this to a non-zero value for next readdir();

		noError();

		// list might still be empty (empty dirs)

		if (!(d->dirList)) return 0;

		if (d->pdirList) {

			SMBdirent *ret = &(d->pdirList->entry);

			d->pdirList = d->pdirList->next;  // increment counter

			return ret;

		}

		return 0; // end of directory

	}



// Now the most difficult part of all this: correct browsing.

// Because the information is distributed over the network, and the election

// scheme takes time, it's possible (I saw it happening more than once) a

// workgroup master have a wrong idea about who is the real master of the

// other workgroups, and would in that case return an incorrect list in

// domain enumeration. This is also true for the local samba...

// In the native code, there is a good algorithm to do this (perhaps not

// writen clean enough). Here, we use some functions from Samba instead.



	// So now it's a virtual dir => must do browsing

	util.parse(d->file());

	// If there is an host => list the shares

	if (util.host()) {

		int res = getShareList();

		// The descriptor becomes the owner of the list

		d->dirList = SMBdirentList::dirListing;

		SMBdirentList::dirListing = 0;

		// Set the pointer at the begining

		d->pdirList = d->dirList;

		d->fid = 1; // set this to a non-zero value for next readdir();

		if (res==-1) return 0; // error already set in getShareList

		// The only way to know it's an end-of-dir for the caller

		noError(); // and not an error is to look at the error code

		// list might still be empty (empty dirs)

		if (!(d->dirList)) return 0;

		if (d->pdirList) {

			SMBdirent *ret = &(d->pdirList->entry);

			d->pdirList = d->pdirList->next;  // increment counter

			return ret;

		}

		return 0; // end of directory

	}

	



	// If there is a workgroup => list the members

	if (util.workgroup()) {

		// We must find the real master first

		if (findMaster() == -1) {

			findError();

			return 0;

		}

		// Now we have it. Ask it the members for its workgroup

		util.host(theMaster);

		int res = getMemberList();

		// The descriptor becomes the owner of the list

		d->dirList = SMBdirentList::dirListing;

		SMBdirentList::dirListing = 0;

		// Set the pointer at the begining

		d->pdirList = d->dirList;

		d->fid = 1; // set this to a non-zero value for next readdir();

		if (res==-1) return 0; // error already set in getShareList

		// The only way to know it's an end-of-dir for the caller

		noError(); // and not an error is to look at the error code

		// list might still be empty (empty dirs)

		if (!(d->dirList)) return 0;

		if (d->pdirList) {

			SMBdirent *ret = &(d->pdirList->entry);

			d->pdirList = d->pdirList->next;  // increment counter

			return ret;

		}

		return 0; // end of directory

	}

	

	// No workgroup, we'll try to find them all!

	// first find a master for our workgroup

	char *ourGroup = 0;

	newstrcpy(ourGroup, lp_workgroup());

	util.workgroup(ourGroup);

	if (findMaster() == -1) {

		findError();

		if (ourGroup) delete ourGroup;

		return 0;

	}

	// do a domain enum on it

	util.host(theMaster);

	int res = getWorkgroupList();

	if (res==-1) {

		if (ourGroup) delete ourGroup;

		return 0; // error already set in getWorkgroupList

	}

	// for each other workgroup than ours, find the master and ask it the same

	// thing. This way we should find the whole domain controller tree.

	SMBdirentList* first = SMBdirentList::dirListing;

	SMBdirentList::dirListing = 0;  // we own it

	SMBdirentList* final = first;       // the final resulting list

	for (SMBdirentList *it = first; (it); it = it->next) {

		if (mystrcasecmp(ourGroup, it->entry.d_name)) {

			util.workgroup(it->entry.d_name);

			util.host(it->comment);

			if (getWorkgroupList() == -1) continue; // skip the bad guys

			final->merge(); // final non 0

			if (final!=first) delete final; // keep the first list for iterating

			final = SMBdirentList::dirListing;

			SMBdirentList::dirListing = 0;  // got it now

		}

	}

	// cleanup

	if (ourGroup) delete ourGroup;

	if ((first) && (first!=final)) delete first;

	// The descriptor becomes the owner of the final list

	d->dirList = final;

	// Set the pointer at the begining

	d->pdirList = d->dirList;

	d->fid = 1; // set this to a non-zero value for next readdir();

	// The only way to know it's an end-of-dir for the caller

	noError(); // and not an error is to look at the error code

	// list might still be empty (empty dirs)

	if (!(d->dirList)) return 0;

	if (d->pdirList) {

		SMBdirent *ret = &(d->pdirList->entry);

		d->pdirList = d->pdirList->next;  // increment counter

		return ret;

	}

	return 0; // end of directory

}



// This function supposes util contains correct values

int SambaLink::findMaster()

{

	// temporary result

	char *m = 0;

	

	// Try Samba trusted server list

	fstring aname;

	BOOL res = get_any_dc_name(util.workgroup(), aname);

	if (res) {

		// skip Samba double-backslash garbage and convert "." to our name

		if (mystrcmp(aname+2,"\\\\.")) newstrcpy(theMaster, global_myname);

		else newstrcpy(theMaster, aname+2);

		return 0;

	}

	

	// Use user's kindness first: this might very well be the PDC!

	if (browseServer) {

		util.host(browseServer);

		getWorkgroupList();

		// find the master

		newstrcpy(m, SMBdirentList::nameComment(util.workgroup()));

		// found one, is it the correct one?

		if (m) {

			// According to itself, yes => suppose it's the real one

			if (!mystrcasecmp(m, browseServer)) {

				if (theMaster) delete theMaster;

				theMaster = m;  // correctly allocated

				return 0;

			}

			util.host(m);

			getWorkgroupList();

			char *m2 = SMBdirentList::nameComment(util.workgroup());

			// According to itself, yes => suppose it's the real one

			if (!mystrcasecmp(m, m2)) {

				if (theMaster) delete theMaster;

				theMaster = m;  // correctly allocated
				return 0;
			}
			// so, there is an inconsistency. We'll find it differently...
		}
	}

	// The local Samba tried itself if the trusted list was empty above
	// So try it now, but we can expect some inconsistency
	util.host(global_myname);
	getWorkgroupList();
	// find the master
	newstrcpy(m, SMBdirentList::nameComment(util.workgroup()));
	// found one, is it the correct one?
	if (m) {
		if (!mystrcasecmp(m, global_myname)) { // Hey, weird, it's us!
			if (theMaster) delete theMaster;
			theMaster = m;  // correctly allocated
			return 0;
		}
		util.host(m);
		getWorkgroupList();
		char *m2 = SMBdirentList::nameComment(util.workgroup());
		// According to itself, yes => suppose it's the real one
		if (!mystrcasecmp(m, m2)) {
			if (theMaster) delete theMaster;
			theMaster = m;  // correctly allocated
			return 0;
		}
		// so, there is an inconsistency. We'll find it differently...
	}
	
	// hmm, try an IP trick
	struct in_addr ip;
	res = find_master_ip(util.workgroup(), &ip);
	if (res) {
		util.host(0);
		util.ip(inet_ntoa(ip));
		getWorkgroupList();
		// if found one, OK
		newstrcpy(theMaster, SMBdirentList::nameComment(util.workgroup()));
		if (theMaster) return 0;
	}

	// Never mind...
	return -1;
}

int SambaLink::closedir(int dirdesc)
{
	Descriptor *d = Descriptor::find(dirdesc);
	if ((!d) || (!d->dirdesc)) return findError(EBADF);
	delete d;  // also close and shut the client down
	return noError();
}

// OK, the standard version returns nothing. Here -1 = error (0 otherwise)
int SambaLink::rewinddir(int dirdesc)
{
	Descriptor *d = Descriptor::find(dirdesc);
	if ((!d) || (!d->dirdesc)) return findError(EBADF);
	
	// reset 'stream' pointer whether it's null or not
	d->pdirList = d->dirList;
	
	// That's all folks!
	return noError();
}

// return the last error encountered when a function returns -1	
int SambaLink::error()
{
	return lastError;
}

// AAAhh, it's 22:41, 01 Feb 2000, and I finished implementing all the functions
// in a crazy week!
// Enjoy :-)
//   Nicolas Brodu

// 20000329: Added these to make samba link work again
// from samba/source/lib/stub_uid.c
/* we need these because we link to locking*.o */
extern "C" {
void become_root(BOOL save_dir)
{
}

void unbecome_root(BOOL restore_dir)
{
}
}

int SambaLink::closedir(int dirdesc)

{

	Descriptor *d = Descriptor::find(dirdesc);

	if ((!d) || (!d->dirdesc)) return findError(EBADF);

	delete d;  // also close and shut the client down

	return noError();

}



// OK, the standard version returns nothing. Here -1 = error (0 otherwise)

int SambaLink::rewinddir(int dirdesc)

{

	Descriptor *d = Descriptor::find(dirdesc);

	if ((!d) || (!d->dirdesc)) return findError(EBADF);

	

	// reset 'stream' pointer whether it's null or not

	d->pdirList = d->dirList;

	

	// That's all folks!

	return noError();

}



// return the last error encountered when a function returns -1	

int SambaLink::error()

{

	return lastError;

}



// AAAhh, it's 22:41, 01 Feb 2000, and I finished implementing all the functions

// in a crazy week!

// Enjoy :-)

//   Nicolas Brodu



// 20000329: Added these to make samba link work again

// from samba/source/lib/stub_uid.c

/* we need these because we link to locking*.o */

extern "C" {

void become_root(BOOL save_dir)

{

}



void unbecome_root(BOOL restore_dir)

{

}

}

#endif

#pragma code_seg()
#pragma data_seg()
#pragma bss_seg()
#pragma const_seg()

